﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using CoreEx.Common.Extensions;
using CoreEx.Database.Metadata;
using CoreEx.Database.Query;
using LinFu.IoC.Interfaces;
using LinFu.IoC;
using System.Collections;

namespace CoreEx.Database.Linq.Implementation
{
    public class JoinBasedExpressionTranslator : ExpressionTranslatorBase
    {                
        [ThreadStatic]
        private static IDictionary<MemberInfo,string> _aliasMap = new Dictionary<MemberInfo, string>();       

        [ThreadStatic]
        private static IDictionary<Type, IList<IRelationLoadTarget>> _relationLoadTargets;


        private readonly IDictionary<string,int> _columnNames = new Dictionary<string, int>(); 


        public JoinBasedExpressionTranslator(string connectionName)
            : base(connectionName)
        {
            if (_aliasMap == null)
                _aliasMap = new Dictionary<MemberInfo, string>();

        }




        private void ExtractLoadExpressions(Expression expression)
        {
            var extractor = ServiceContainer.GetService<IRelationLoadTargetExtractor>(MappingRepository);
            _relationLoadTargets = extractor.ExtractFrom(expression);                               
        }

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            if (_translateResult != null && _translateResult.Expression != null)
                return _translateResult.Expression.ToString();
            return base.ToString();
        }

        protected override ITranslateResult TranslateExpression(Expression expression, PropertyInfo property, IProjectionTarget outerProjectionTarget)
        {
            _translateResult = new TranslateResult();
            var methodCallExpression = (MethodCallExpression) expression;

            var subQueryExpression = (SelectQueryExpression)Visit(methodCallExpression.Arguments[0]);                                 
            if (methodCallExpression.Arguments.Count == 2)
            {
                var lambdaExpression = ((LambdaExpression)StripQuotes(methodCallExpression.Arguments[1])).Evaluate();
                var whereClause = Visit(lambdaExpression);
                subQueryExpression.Where(whereClause);
            }

            CreateFinalQuery();

                       
            ((ISelectExpression)subQueryExpression).ProjectionExpression =
                        ExpressionFactory.Constant(1);
            ((ISelectExpression) subQueryExpression).IsSubQuery = true;


            

            var typeMappingInfo = outerProjectionTarget.TypeMappingInfo;
            var foreignKeyInfo = typeMappingInfo.ColumnMappings[property].ForeignKeyInfo;
            var thisAlias = _translateResult.ProjectionTargets.First().Value.Alias;
            var outerAlias = outerProjectionTarget.Alias;


            foreach (var foreignkeyColumnInfo in foreignKeyInfo.Columns)
            {
                subQueryExpression.Where(
                    ExpressionFactory.Column(thisAlias,
                                             foreignkeyColumnInfo.ForeignColumnInfo.ColumnName) ==
                    ExpressionFactory.Column(outerAlias,
                                             foreignkeyColumnInfo.PrimaryColumnInfo.ColumnName));
            }
            _translateResult.DbExpression = subQueryExpression;
            return _translateResult;
        }

        /// <summary>
        /// Translates the expression into an <see cref="ITranslateResult"/> instance.
        /// </summary>
        /// <param name="expression">The <see cref="Expression"/> to translate</param>
        /// <returns></returns>
        protected override ITranslateResult TranslateExpression(Expression expression)
        {
            _aliasMap.Clear();
            ExtractLoadExpressions(expression);
            return TranslateExpression(expression, null, false);
        }

        protected override ITranslateResult TranslateExpression(Expression expression, ITranslateResult parent, bool createSubQuery)
        {            
            
            _translateResult = new TranslateResult();                       
            _translateResult.DbExpression = Visit(expression);
            _translateResult.ProjectedType = expression.Type.GetGenericElementType();
            var rootType = (Type)_translateResult.ProjectionTargets.First().Value.Member;                        
            TranslateManyToOneLoadExpressions(rootType);            
            CreateFinalQuery();
            TranslateOneToManyLoadExpressions();
                        
            //ProjectPrimaryKeyFromRootType();

            if (createSubQuery)
            {
                //TransformToSubQuery(parent);
            }            
            return _translateResult;
        }

        protected override ITranslateResult TranslateExpression(IRelationLoadTarget relationLoadTarget, IProjectionTarget outerProjectionTarget, ITranslateResult outerTranslateResult)
        {
            _translateResult = new TranslateResult();
            _translateResult.DbExpression = Visit(relationLoadTarget.LoadExpression);
            
            var rootType = (Type)_translateResult.ProjectionTargets.First().Value.Member;
            TranslateManyToOneLoadExpressions(rootType);
            CreateFinalQuery();
            ApplyOuterResult(relationLoadTarget, outerTranslateResult, outerProjectionTarget);
            TranslateOneToManyLoadExpressions();

            return _translateResult;
        }

        private void ApplyOuterResult(IRelationLoadTarget relationLoadTarget, ITranslateResult outerTranslateResult, IProjectionTarget outerProjectionTarget)
        {
            var outerSelectExpression = RemoveProjectionFromTranslateResult(outerTranslateResult);
            var foreignKeyInfo = relationLoadTarget.PropertyMapping.ForeignKeyInfo;
            var thisAlias = _translateResult.ProjectionTargets.First().Value.Alias;
            var outerAlias = outerProjectionTarget.Alias;

            foreach (var foreignkeyColumnInfo in foreignKeyInfo.Columns)
            {
                outerSelectExpression.Where(
                    ExpressionFactory.Column(thisAlias,
                                             foreignkeyColumnInfo.ForeignColumnInfo.ColumnName) ==
                    ExpressionFactory.Column(outerAlias,
                                             foreignkeyColumnInfo.PrimaryColumnInfo.ColumnName));
            }
            IList<IExpression> projectionList = new List<IExpression>();
            var projection = ((ISelectExpression) _translateResult.DbExpression).ProjectionExpression;
            var foreignKeyColumns = foreignKeyInfo.Columns.Select(fkc =>
                    ExpressionFactory.Column(thisAlias,
                                             fkc.PrimaryColumnInfo.ColumnName)).ToList();

            foreach (var foreignKeyColumn in foreignKeyColumns)
            {
                if (!IsProjected(foreignKeyColumn,projection))
                {
                    projectionList.Add(foreignKeyColumn);
                }
            }
            projectionList.Add(projection);
            _translateResult.RelationLoadTarget = relationLoadTarget;
            ((ISelectExpression) _translateResult.DbExpression).ProjectionExpression = ExpressionFactory.List(projectionList);
            ((SelectQueryExpression) _translateResult.DbExpression).Where(e => e.Exists(outerSelectExpression));
        }

        private bool IsProjected(IExpression expression, IExpression projection)
        {
            IExpressionList list = (projection as IExpressionList);
            if (list == null)
                return expression.Equals(projection);


            foreach (var dbExpression in list.Expressions)
            {
                if (dbExpression is IExpressionList)
                {
                    if (IsProjected(expression, dbExpression))
                        return true;
                }
                else
                {
                    if (dbExpression.Equals(expression))
                        return true;

                }
            }
            return false;
        }


        /// <summary>
        /// Removes the projection from translate result.
        /// </summary>
        /// <param name="outerResult">The outer result.</param>
        /// <returns></returns>
        private SelectQueryExpression RemoveProjectionFromTranslateResult(ITranslateResult outerResult)
        {
            var query = ServiceContainer.GetService<IQuery>();

            var parentExpression = (ISelectExpression)outerResult.DbExpression;

            var selectQuery = query.Select(e => e.Constant(1)).From(parentExpression.FromExpression)
                .Where(parentExpression.WhereExpression);

            return selectQuery;
        }


        /// <summary>
        /// Creates the final query.
        /// </summary>
        private void CreateFinalQuery()
        {
            var rootProjectionTarget = _translateResult.ProjectionTargets.Values.First();
            var selectQueryExpression = (ISelectExpression)rootProjectionTarget.DbExpression;

            IExpression fromClause = null;
            IList<IExpression> projectionList = new List<IExpression>();
            
            foreach (var projectionTarget in _translateResult.ProjectionTargets.Values)
            {
                if (fromClause == null)
                    fromClause = selectQueryExpression.FromExpression;
                else
                    fromClause = ExpressionFactory.Composite(fromClause, projectionTarget.DbExpression);

                if (projectionTarget.Projection != null)
                    projectionList.Add(projectionTarget.Projection);

           }
            selectQueryExpression.FromExpression = fromClause;
            selectQueryExpression.ProjectionExpression = ExpressionFactory.List(projectionList);            
        }

        

        

        private void TranslateManyToOneLoadExpressions(Type targetType)
        {
            if (!_relationLoadTargets.ContainsKey(targetType))
                return;

            var relationTargets =
                _relationLoadTargets[targetType].Where(rt => rt.PropertyMapping.RelationType == CardinalityType.ManyToOne || rt.PropertyMapping.RelationType == CardinalityType.OneToOne);

            foreach (var relationTarget in relationTargets)
            {
                //Make sure that we don't run into recursion that causes a stack overflow
                if (relationTarget.IsLoaded)
                    continue;
                relationTarget.IsLoaded = true;
                var projectionTarget = GetProjectionTarget((MemberExpression)relationTarget.LoadExpression.Body);
                projectionTarget.Projection = CreateProjectionFromProjectionTarget(projectionTarget);
                //Make sure that we include load expressions targeting the currently translated load expression.                                        
                TranslateManyToOneLoadExpressions(relationTarget.TargetProperty.PropertyType);
            }                        
        }


        private void TranslateOneToManyLoadExpressions()
        {
            //Find all projectin targets that actually projects columns from the database
            var projectionTargets = _translateResult.ProjectionTargets.Values.Where(p => p.Projection != null);

            foreach (var projectionTarget in projectionTargets)
            {
                //get the type that the projection target projects
                var projectionMemberType = projectionTarget.GetMemberType();

                if (!_relationLoadTargets.ContainsKey(projectionMemberType))
                    continue;

                var relationTargets =
                _relationLoadTargets[projectionMemberType].Where(rt => rt.PropertyMapping.RelationType == CardinalityType.OneToMany);

                foreach (var relationTarget in relationTargets)
                {
                    //Make sure that we don't run into recursion that causes a stack overflow
                    if (relationTarget.IsLoaded)
                        continue;
                    relationTarget.IsLoaded = true;

                    var expressionTranslator = ServiceContainer.GetService<IExpressionTranslator>();
                    var translateResult = expressionTranslator.Translate(relationTarget,projectionTarget, _translateResult);
                    _translateResult.MemberResults.Add(relationTarget.TargetProperty,translateResult);
                }
            }
        }




        


        private bool IsRelational(MemberInfo memberInfo)
        {
            Type type;

            PropertyInfo propertyInfo = (memberInfo as PropertyInfo);
            if (propertyInfo == null)
            {
                type = (memberInfo as Type);
                if (type == null)
                    return false;
            }
            else
                type = propertyInfo.PropertyType;

            var elementType = type.GetGenericElementType();
            if (elementType.IsSystemType() && !elementType.IsEnumerable())
                return false;
            return true;
        }

        private bool IsEnumerable(MemberInfo memberInfo)
        {
            PropertyInfo propertyInfo = (memberInfo as PropertyInfo);
            return propertyInfo.PropertyType.IsEnumerable();
        }



        protected override IExpression VisitMemberInit(MemberInitExpression memberInitExpression)
        {
            throw new NotImplementedException();
        }

        protected override IExpression VisitConditional(ConditionalExpression conditionalExpression)
        {
            throw new NotImplementedException();
        }

        protected override IExpression VisitUnary(UnaryExpression expression)
        {
            if (expression.Type == typeof(bool) && expression.NodeType == ExpressionType.Not)
                return ExpressionFactory.Not(Visit(expression.Operand));


            return Visit(expression.Operand);
        }

        protected override IExpression VisitMethodCall(MethodCallExpression expression)
        {
            switch (expression.Method.Name)
            {
                case "Where" :
                    return HandleWhere(expression);
                case "Select":
                    return HandleSelect(expression);
                case "Contains":
                    return HandleContains(expression);
                case "Any":
                    return HandleAny(expression);
                case "First":
                    return HandleFirst(expression);
                case "OrderBy":
                    return HandleOrderBy(expression);
                case "ThenBy":
                    return HandleOrderBy(expression);
                case "OrderByDescending":
                    return HandleOrderByDescending(expression);
                case "ThenByDescending":
                    return HandleOrderByDescending(expression);
                case "Take":
                    return HandleTake(expression);
                

                default:
                    throw new NotSupportedException(string.Format("Method {0} is not supported", expression.Method.Name));
            }
        }


        protected virtual IExpression HandleWhere(MethodCallExpression methodCallExpression)
        {                                    
            var lambdaExpression = ((LambdaExpression)StripQuotes(methodCallExpression.Arguments[1])).Evaluate();
            var selectQueryExpression = (SelectQueryExpression)Visit(methodCallExpression.Arguments[0]);
            var whereClause = Visit(lambdaExpression);
            return selectQueryExpression.Where(whereClause);
        }

        protected virtual IExpression HandleSelect(MethodCallExpression methodCallExpression)
        {
            var lambdaExpression = ((LambdaExpression)StripQuotes(methodCallExpression.Arguments[1])).Evaluate();
            var selectQueryExpression = (SelectQueryExpression)Visit(methodCallExpression.Arguments[0]);
            ((ISelectExpression)selectQueryExpression).ProjectionExpression = Visit(lambdaExpression);
            return selectQueryExpression;
        }


        protected virtual IExpression HandleContains(MethodCallExpression methodCallExpression)
        {
            if (methodCallExpression.Arguments.Count == 2)
            {
                var values = ((ConstantExpression)methodCallExpression.Arguments[0]).Value;
                var valueList = ConvertListToObjectArray((IEnumerable)values);
                var expression = ExpressionFactory.In(Visit(methodCallExpression.Arguments[1]), valueList);
                return expression;
            }
            if (methodCallExpression.Arguments.Count == 1)
            {
                //May happen when passing in e.g. IList<string> rather than string[];
                if (methodCallExpression.Object is ConstantExpression)
                {
                    var values = ((ConstantExpression)methodCallExpression.Object).Value;
                    var valueList = ConvertListToObjectArray((IEnumerable)values);
                    var expression = ExpressionFactory.In(Visit(methodCallExpression.Arguments[0]), valueList.ToArray());
                    return expression;
                }
                else
                {
                    var expression = ExpressionFactory.Contains(Visit(methodCallExpression.Object), Visit(methodCallExpression.Arguments[0]));
                    return expression;
                }

            }
            throw new ArgumentOutOfRangeException("methodCallExpression", methodCallExpression,"Could not translate MethodCallExpression(Contains");
        }


        protected virtual IExpression HandleAny(MethodCallExpression methodCallExpression)
        {
            var expressionTranslator = ServiceContainer.GetService<IExpressionTranslator>();
            var memberExpression = (MemberExpression) methodCallExpression.Arguments[0];
            var property = (PropertyInfo) memberExpression.Member;
            var projectionTarget = GetProjectionTarget(memberExpression);
                     
            var subQueryResult = expressionTranslator.Translate(methodCallExpression, property, projectionTarget);
                                   
            var expression = ExpressionFactory.Exists((SelectQueryExpression)subQueryResult.DbExpression);
            return expression;
        }


        protected virtual IExpression HandleFirst(MethodCallExpression methodCallExpression)
        {
            var selectQueryExpression = (SelectQueryExpression)Visit(methodCallExpression.Arguments[0]);
            if (methodCallExpression.Arguments.Count == 2)
            {
                var lambdaExpression = ((LambdaExpression)StripQuotes(methodCallExpression.Arguments[1])).Evaluate();
                var whereClause = Visit(lambdaExpression);
                selectQueryExpression.Where(whereClause);
            }
            selectQueryExpression.Take(1);
            return selectQueryExpression;
        }


        protected virtual IExpression HandleOrderBy(MethodCallExpression methodCallExpression)
        {
            var selectQueryExpression = (SelectQueryExpression)Visit(methodCallExpression.Arguments[0]);
            var lambdaExpression = ((LambdaExpression)StripQuotes(methodCallExpression.Arguments[1])).Evaluate();
            selectQueryExpression.OrderBy(Visit(lambdaExpression));
            return selectQueryExpression;
        }

        protected virtual IExpression HandleOrderByDescending(MethodCallExpression methodCallExpression)
        {
            var selectQueryExpression = (SelectQueryExpression)Visit(methodCallExpression.Arguments[0]);
            var lambdaExpression = ((LambdaExpression)StripQuotes(methodCallExpression.Arguments[1])).Evaluate();
            selectQueryExpression.OrderBy(Visit(lambdaExpression),SortDirection.Descending);
            return selectQueryExpression;
        }


        protected virtual IExpression HandleTake(MethodCallExpression methodCallExpression)
        {
            var selectQueryExpression = (SelectQueryExpression)Visit(methodCallExpression.Arguments[0]);
            var constantExpression = (ConstantExpression)methodCallExpression.Arguments[1];
            selectQueryExpression.Take((int) constantExpression.Value);
            return selectQueryExpression;
        }



        private object[] ConvertListToObjectArray(IEnumerable list)
        {
            IList<object> valueList = new List<object>();
            foreach (var element in list)
            {
                valueList.Add(element);
            }
            return valueList.ToArray();
        }

        private IExpression CreateProjectionFromProjectionTarget(IProjectionTarget projectionTarget)
        {
            IList<IExpression> columnList = new List<IExpression>();
            
            var columnMappings =
                projectionTarget.TypeMappingInfo.ColumnMappings.Values.Where(cm => cm.ColumnInfo != null);

            var primaryKeyColumns = projectionTarget.TypeMappingInfo.TableInfo.PrimaryKey.Columns;
            var primaryKeyColumnNames = new List<string>();
            foreach (var columnMapping in columnMappings)
            {
                var columnName = columnMapping.ColumnInfo.ColumnName;
                var proposedAlias = GetUniqueColumnName(columnName);
                
                if (proposedAlias != columnName)
                {
                    var expression = ExpressionFactory.Alias(e => e.Column(projectionTarget.Alias, columnName), proposedAlias);
                    columnList.Add(expression);
                    projectionTarget.MemberMap.Add(columnMapping.Property, proposedAlias);
                    if (primaryKeyColumns.Contains(columnMapping.ColumnInfo))
                        primaryKeyColumnNames.Add(proposedAlias);
                }
                else
                {
                    var expression = ExpressionFactory.Column(projectionTarget.Alias, columnName);
                    columnList.Add(expression);
                    projectionTarget.MemberMap.Add(columnMapping.Property, columnName);
                    if (primaryKeyColumns.Contains(columnMapping.ColumnInfo))
                        primaryKeyColumnNames.Add(columnName);
                }

            }
            projectionTarget.PrimaryKeyColumnNames = primaryKeyColumnNames.ToArray();


            return ExpressionFactory.List(columnList);
        }



        private string GetUniqueColumnName(string columnName)
        {
            if (!_columnNames.ContainsKey(columnName))
            {
                _columnNames.Add(columnName,1);
                return columnName;
            }
            
            _columnNames[columnName]++;
            return string.Format("{0}{1}", columnName, _columnNames[columnName]);
        }

        protected override IExpression VisitConstant(ConstantExpression constantExpression)
        {
            var queryable = constantExpression.Value as IQueryable;
            if (queryable != null)
            {
                var elementType = queryable.ElementType;
                return CreateRootQuery(elementType);                
            }
            return ExpressionFactory.Constant(constantExpression.Value);
        }


        private string GetMemberAlias(MemberInfo memberInfo)
        {
            if (!_aliasMap.ContainsKey(memberInfo))
            {
                _aliasMap.Add(memberInfo,string.Format("t{0}",_aliasMap.Count));
            }
            return _aliasMap[memberInfo];
        }
        

        

        protected override IExpression VisitMemberAccess(MemberExpression memberExpression)
        {
            
            if (IsRelational(memberExpression.Member))
            {
                var elementType = memberExpression.Type.GetGenericElementType();
                return CreateRootQuery(elementType);
            }
            else
            {
                var projectionTarget = GetProjectionTarget(memberExpression);
                return ExpressionFactory.Column(projectionTarget.Alias,
                                                projectionTarget.TypeMappingInfo.ColumnMappings[
                                                    (PropertyInfo) memberExpression.Member].ColumnInfo.ColumnName);
            }
        }

        private IExpression CreateRootQuery(Type elementType)
        {           
            var projectionTarget = new Projectiontarget();                
            projectionTarget.Member = elementType;
            projectionTarget.Alias = GetMemberAlias(projectionTarget.Member);
            projectionTarget.TypeMappingInfo = MappingRepository[elementType];
            _translateResult.ProjectionTargets.Add(elementType, projectionTarget);
            var table = ExpressionFactory.Table(projectionTarget.TypeMappingInfo.TableInfo.TableName, projectionTarget.Alias);
            var projection = CreateProjectionFromProjectionTarget(projectionTarget);
            var query = ServiceContainer.GetService<IQuery>().Select(projection).From(table);
            projectionTarget.Projection = projection;
            projectionTarget.DbExpression = query;
            return query;
        }

        private IProjectionTarget GetProjectionTarget(MemberExpression memberExpression)
        {
            Expression containingExpression;

            if (!IsRelational(memberExpression.Member) || IsEnumerable(memberExpression.Member))
                containingExpression = memberExpression.Expression;
            else
                containingExpression = memberExpression;
          
            if (containingExpression.NodeType == ExpressionType.Parameter)            
                return _translateResult.ProjectionTargets[containingExpression.Type];
            
            if (containingExpression.NodeType == ExpressionType.MemberAccess)
            {
                var containingMemberExpression = (MemberExpression)containingExpression;
               
                if (!_translateResult.ProjectionTargets.ContainsKey(containingMemberExpression.Member))
                {
                    CreateJoinsNeededForMemberAccess(containingMemberExpression);
                    return _translateResult.ProjectionTargets[containingMemberExpression.Member];
                }

                return _translateResult.ProjectionTargets[containingMemberExpression.Member];
            }
            return null;
        }

        private void CreateJoinsNeededForMemberAccess(MemberExpression memberExpression)
        {
            Stack<MemberExpression> candidateMemberExpressions = GetMemberJoinCandidates(memberExpression);

            foreach (var candidateMemberExpression in candidateMemberExpressions)
            {                
                IProjectionTarget outerProjectionTarget = GetOuterProjectionTargetForJoinCandidate(candidateMemberExpression);                
                var propertyInfo = (PropertyInfo)candidateMemberExpression.Member;
                var outerTypeMappingInfo = outerProjectionTarget.TypeMappingInfo;
                var foreignKeyInfo = outerTypeMappingInfo.ColumnMappings[propertyInfo].ForeignKeyInfo;
                var currentAlias = outerProjectionTarget.Alias;
                var nextAlias = GetMemberAlias(candidateMemberExpression.Member);

                IExpression criteriaExpression = null;
                foreach (var foreignKeyColumnInfo in foreignKeyInfo.Columns)
                {
                    IExpression equalsExpression;
                    //One to One relations
                    if (foreignKeyInfo.PrimaryTable == outerProjectionTarget.TypeMappingInfo.TableInfo)
                    {
                        equalsExpression =
                         ExpressionFactory.Column(currentAlias, foreignKeyColumnInfo.PrimaryColumnInfo.ColumnName) ==
                         ExpressionFactory.Column(nextAlias, foreignKeyColumnInfo.ForeignColumnInfo.ColumnName);
                    }
                    else
                    {
                        equalsExpression =
                            ExpressionFactory.Column(currentAlias, foreignKeyColumnInfo.ForeignColumnInfo.ColumnName) ==
                            ExpressionFactory.Column(nextAlias, foreignKeyColumnInfo.PrimaryColumnInfo.ColumnName);
                    }
                    if (criteriaExpression == null)
                        criteriaExpression = equalsExpression;
                    else
                        criteriaExpression = (ExpressionBase)criteriaExpression & (ExpressionBase)equalsExpression;
                }

                IExpression joinExpression;

                //One to One relations
                if (foreignKeyInfo.PrimaryTable == outerProjectionTarget.TypeMappingInfo.TableInfo)
                {
                    joinExpression =  ExpressionFactory.Join(JoinType.LeftOuter,
                                           ExpressionFactory.Table(foreignKeyInfo.Table.TableName, nextAlias),
                                           criteriaExpression);
                }
                else
                joinExpression = ExpressionFactory.Join(JoinType.LeftOuter,
                                                           ExpressionFactory.Table(
                                                               foreignKeyInfo.PrimaryTable.TableName, nextAlias),
                                                           criteriaExpression);


                Projectiontarget joinProjectionTarget = new Projectiontarget();
                joinProjectionTarget.Alias = nextAlias;
                joinProjectionTarget.Member = candidateMemberExpression.Member;
                joinProjectionTarget.TypeMappingInfo = MappingRepository[propertyInfo.PropertyType];
                joinProjectionTarget.DbExpression = joinExpression;
                joinProjectionTarget.Parent = outerProjectionTarget;
                outerProjectionTarget.Children.Add(joinProjectionTarget);
                _translateResult.ProjectionTargets.Add(candidateMemberExpression.Member,joinProjectionTarget);
            }
        }

        private IProjectionTarget GetOuterProjectionTargetForJoinCandidate(MemberExpression candidateMemberExpression)
        {
            if (candidateMemberExpression.Expression is ParameterExpression)
            {
                var projectionTarget = _translateResult.ProjectionTargets.Where(
                    p => p.Value.GetMemberType() == candidateMemberExpression.Expression.Type).FirstOrDefault();
                return projectionTarget.Value;
                //return _translateResult.ProjectionTargets[candidateMemberExpression.Expression.Type];                                
            }
            return
                _translateResult.ProjectionTargets[((MemberExpression) candidateMemberExpression.Expression).Member];            
        }

        private Stack<MemberExpression> GetMemberJoinCandidates(MemberExpression memberExpression)
        {
            Stack<MemberExpression> memberExpressions = new Stack<MemberExpression>();
           
            while (memberExpression.Expression is MemberExpression)
            {               
                if (!_translateResult.ProjectionTargets.ContainsKey(memberExpression.Member))                
                    memberExpressions.Push(memberExpression);
                memberExpression = (MemberExpression)memberExpression.Expression;
            }
            if (!_translateResult.ProjectionTargets.ContainsKey(memberExpression.Member))
                memberExpressions.Push(memberExpression);
            return memberExpressions;
        }


        protected override IExpression VisitBinary(BinaryExpression binaryExpression)
        {
            switch (binaryExpression.NodeType)
            {
                case ExpressionType.Equal:
                    return ExpressionFactory.Eq(Visit(binaryExpression.Left), Visit(binaryExpression.Right));
                case ExpressionType.GreaterThan:
                    return ExpressionFactory.GreaterThan(Visit(binaryExpression.Left), Visit(binaryExpression.Right));
                case ExpressionType.GreaterThanOrEqual:
                    return ExpressionFactory.GreaterOrEqual(Visit(binaryExpression.Left), Visit(binaryExpression.Right));
                case ExpressionType.LessThan:
                    return ExpressionFactory.LessThan(Visit(binaryExpression.Left), Visit(binaryExpression.Right));
                case ExpressionType.LessThanOrEqual:
                    return ExpressionFactory.LessOrEqual(Visit(binaryExpression.Left), Visit(binaryExpression.Right));
                case ExpressionType.Or:
                case ExpressionType.OrElse:
                    return ExpressionFactory.Or(Visit(binaryExpression.Left), Visit(binaryExpression.Right));
                case ExpressionType.And:
                case ExpressionType.AndAlso:
                    return ExpressionFactory.And(Visit(binaryExpression.Left), Visit(binaryExpression.Right));
                case ExpressionType.Add:
                    return ExpressionFactory.Add(Visit(binaryExpression.Left), Visit(binaryExpression.Right));
                case ExpressionType.Subtract:
                    return ExpressionFactory.Subtract(Visit(binaryExpression.Left), Visit(binaryExpression.Right));
                case ExpressionType.Multiply:
                    return ExpressionFactory.Multiply(Visit(binaryExpression.Left), Visit(binaryExpression.Right));
                case ExpressionType.Divide:
                    return ExpressionFactory.Divide(Visit(binaryExpression.Left), Visit(binaryExpression.Right));
                default:
                    throw new NotSupportedException(string.Format("BinaryExpression ({0}) not supported", binaryExpression.NodeType));
            }



        }

        protected override IExpression VisitParameter(ParameterExpression expression)
        {
            throw new NotImplementedException();
        }

        protected override IExpression VisitNew(NewExpression expression)
        {
            IList<IExpression> projectionList = new List<IExpression>();

            foreach (var argument in expression.Arguments)
            {
                if (argument is MemberExpression)
                {
                    projectionList.Add(Visit(argument));
                }
            }
            var list = ExpressionFactory.List(projectionList);
            return list;
        }
    }
}
